/*
 * Galaxium Messenger
 * Copyright (C) 2007 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Gdk;
using Gtk;

using Anculus.Core;

using Galaxium.Protocol;

namespace Galaxium.Gui.GtkGui
{
	public class EntityTag: TextTag
	{
		const uint MIN_BRIGHTNESS_CONTRAST = 75;
		const uint MIN_COLOR_CONTRAST = 200;
		
		static Gdk.Color _sendColor = new Gdk.Color(22, 86, 158);
		static List<Gdk.Color> _nickColors;
		static Gdk.Color[] _nickSeedColors = {
			new Gdk.Color(252, 233, 79),	// Butter #1
			new Gdk.Color(237, 212, 0),		// Butter #2
			new Gdk.Color(196, 160, 0),		// Butter #3
			new Gdk.Color(252, 175, 62),	// Orange #1
			new Gdk.Color(245, 121, 0),		// Orange #2
			new Gdk.Color(206, 92, 0),		// Orange #3
			new Gdk.Color(233, 185, 110),	// Chocolate #1
			new Gdk.Color(193, 125, 17),	// Chocolate #2
			new Gdk.Color(143, 89, 2),		// Chocolate #3
			new Gdk.Color(138, 226, 52),	// Chameleon #1
			new Gdk.Color(115, 210, 22),	// Chameleon #2
			new Gdk.Color(78, 154, 6),		// Chameleon #3
			new Gdk.Color(114, 159, 207),	// Sky Blue #1
			new Gdk.Color(52, 101, 164),	// Sky Blue #2
			new Gdk.Color(32, 74, 135),		// Sky Blue #3
			new Gdk.Color(173, 127, 168),	// Plum #1
			new Gdk.Color(117, 80, 123),	// Plum #2
			new Gdk.Color(92, 53, 102),		// Plum #3
			new Gdk.Color(239, 41, 41),		// Scarlet Red #1
			new Gdk.Color(204, 0, 0),		// Scarlet Red #2
			new Gdk.Color(164, 0, 0),		// Scarlet Red #3
			new Gdk.Color(136, 138, 133),	// Aluminium #4
			new Gdk.Color(85, 87, 83),		// Aluminium #5
			new Gdk.Color(46, 52, 54),		// Aluminium #6
		};

		TextTagTable _table;
		IEntity _entity;
		
		static EntityTag ()
		{
			GenerateNickColors(200, new Gdk.Color(255, 255, 255));
		}
		
		public IEntity Entity
		{
			get { return _entity; }
		}		
		
		public EntityTag (TextMessageDisplay display, IEntity entity): base (null)
		{
			_table = display._tagTable;
			_entity = entity;
			
			Weight = Pango.Weight.Bold;
			PixelsAboveLines = 5;

			if (entity.Local)
				ForegroundGdk = _sendColor;
			else
			{
				Gdk.Color col = _nickColors[(int)(GetStringHash(entity.UniqueIdentifier) % _nickColors.Count)];

				float scale = ((1 - (Luminance (display.Style.Base (StateType.Normal)) / Luminance (display.Style.White))) *
				               (Luminance (display.Style.White) / Math.Max (Math.Max (col.Red, col.Blue), col.Green)));

				// Only lighten colors, we should never need to darken them
				if (scale > 1)
				{
					col.Red   = (ushort)(scale * col.Red);
					col.Green = (ushort)(scale * col.Green);
					col.Blue  = (ushort)(scale * col.Blue);
				}
				
				ForegroundGdk = col;
			}
			
			_table.Add (this);
		}
		
		public override void Dispose()
		{
			_table.Remove (this);
		}
		
		// Adapted from http://mail.gnome.org/archives/gtk-devel-list/2000-February/msg00045.html
		uint GetStringHash (string str)
		{
			uint result = 0;
			
			foreach (char ch in str)
				result += (result << 3) + ch;
			
			return result;
		}
		
		// Adapted from pidgin
		static void GenerateNickColors(uint count, Gdk.Color background)
		{
			_nickColors = new List<Gdk.Color>();
			
			Random rand = new Random(background.Red + background.Green + background.Blue + 1);
			DateTime breakoutTime = DateTime.Now.AddSeconds(3);
			
			// First check the seed colors
			foreach (Gdk.Color seed in _nickSeedColors)
			{
				if (ColorIsVisible(seed, background, MIN_COLOR_CONTRAST, MIN_BRIGHTNESS_CONTRAST) &&
				    ColorIsVisible(seed, _sendColor, MIN_COLOR_CONTRAST / 4, 0))
					_nickColors.Add(seed);
				
				if ((DateTime.Now > breakoutTime) || (_nickColors.Count >= count))
					break;
			}
			
			if (_nickColors.Count >= count)
				return;
			
			// We don't have enough colors from the seed colors, try random ones
			
			while ((_nickColors.Count < count) && (DateTime.Now < breakoutTime))
			{
				Gdk.Color color = new Gdk.Color((byte)(rand.Next() * 255),
				                                (byte)(rand.Next() * 255),
				                                (byte)(rand.Next() * 255));
				
				if (ColorIsVisible(color, background, MIN_COLOR_CONTRAST, MIN_BRIGHTNESS_CONTRAST) &&
				    ColorIsVisible(color, _sendColor, MIN_COLOR_CONTRAST / 4, 0))
					_nickColors.Add(color);
			}
			
			// Check if we still don't have enough
			
			if (_nickColors.Count < count)
				Log.Warn("Unable to generate enough suitable nickname colors ({0}/{1})", _nickColors.Count, count);
		}
		
		// Algorithm from http://www.w3.org/TR/AERT#color-contrast
		static bool ColorIsVisible(Gdk.Color foreground, Gdk.Color background, uint colorContrast, uint brightnessContrast)
		{
			// Convert from 16 bit to 8 bit values
			
			byte fRed = (byte)(foreground.Red >> 8);
			byte fGreen = (byte)(foreground.Green >> 8);
			byte fBlue = (byte)(foreground.Blue >> 8);

			byte bRed = (byte)(background.Red >> 8);
			byte bGreen = (byte)(background.Green >> 8);
			byte bBlue = (byte)(background.Blue >> 8);
			
			// Calculate brightness
			
			ulong fgBrightness = (ulong)((fRed * 299 + fGreen * 587 + fBlue * 114) / 1000);
			ulong bgBrightness = (ulong)((bRed * 299 + bGreen * 587 + bBlue * 114) / 1000);

			// Calculate differences
			
			ulong brDiff = (ulong)Math.Abs((long)(fgBrightness - bgBrightness));
			ulong colDiff = (ulong)Math.Abs(fRed - bRed) + (ulong)Math.Abs(fGreen - bGreen) + (ulong)Math.Abs(fBlue - bBlue);
			
			return ((colDiff > colorContrast) && (brDiff > brightnessContrast));
		}

		static float Luminance(Gdk.Color col)
		{
			return (float)((0.3 * col.Red) + (0.59 * col.Green) + (0.11 * col.Blue));
		}
	}
}
